package assembly;

import force.Force;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import simulation.IUpdatable;
import util.Location;
import util.Sprite;


/**
 * Represents a collection of springs, muscles, masses and heavy masses that is loaded in the
 * assembly
 * and interacts only within itself
 * 
 * @author Scott Valentine
 * @author Dagbedji Fagnisse
 * 
 */
public class Assembly implements IUpdatable<Assembly> {
    /** The center of the force. */
    private Location myCenterOfMassPoint;

    /** list of masses (and heavy masses) in this assembly */
    private List<Mass> myMasses;

    /** List of springs (and muscles) in this assembly */
    private List<Spring> mySprings;

    /** forces that are currently acting on this assembly */
    private List<Force> myGlobalForces;

    /**
     * default constructor, instantiates myMasses and
     * mySprings to have individual masses and springs loaded
     */
    public Assembly () {
        myMasses = new ArrayList<Mass>();
        mySprings = new ArrayList<Spring>();
    }

    /**
     * gives the current masses in this assembly
     * 
     * @return list of masses in the assembly
     */
    public List<Mass> getMasses () {
        return myMasses;
    }

    /**
     * changes the masses currently in the assembly
     * 
     * @param massLists - the list of masses to put in the assembly
     */
    public void setMassLists (List<Mass> massLists) {
        this.myMasses = massLists;
    }

    /**
     * returns the current center of mass for this assembly
     * 
     * @return the center of pass location
     */
    public Location getCenterOfMass () {
        return myCenterOfMassPoint;
    }

    /**
     * gives a list of springs currently in the assembly
     * 
     * @return - a list of spring in the assembly
     */
    public List<Spring> getSprings () {
        return mySprings;
    }

    /**
     * changes the springs currently in the assembly
     * 
     * @param springs - list of springs to have in the assembly
     */
    public void setMySprings (List<Spring> springs) {
        this.mySprings = springs;
    }

    /**
     * Set the center based on the masses provided.
     * 
     * @param masses is the list of all current masses in the simulation
     */
    private void setCenter (List<Mass> masses) {
        if (masses.size() == 1) {
            myCenterOfMassPoint.setLocation(masses.get(0).getCenter());
            return;
        }
        double x = 0;
        double y = 0;
        double totalMass = 0;
        for (Mass m : masses) {
            double mass = m.getMass();
            x += m.getX() * mass;
            y += m.getY() * mass;
            totalMass += mass;
        }
        myCenterOfMassPoint.setLocation(x / totalMass, y / totalMass);
    }

    /**
     * adds a new sprite to either mySprings or myMasses
     * if passed sprite is neither Spring or Mass, will not add sprite
     * 
     * @param s - sprite to be added
     */
    public void add (Sprite s) {
        if (s instanceof Spring) {
            add((Spring) s);
        }
        else if (s instanceof Mass) {
            add((Mass) s);
        }
    }

    /**
     * adds a spring to this assembly
     * 
     * @param sp is the spring to be added to the assembly
     */
    private void add (Spring sp) {
        mySprings.add(sp);
    }

    /**
     * adds a mass to this assembly
     * 
     * @param m is the mass to be added to the assembly
     */
    private void add (Mass m) {
        myMasses.add(m);
    }

    /**
     * paints all of the elements in this assembly to the canvas
     * 
     * @param pen is used for painting the sprites (springs and masses) in the assembly
     */
    public void paint (Graphics2D pen) {
        for (Spring s : mySprings) {
            s.paint(pen);
        }
        for (Mass m : myMasses) {
            m.paint(pen);
        }

    }

    /**
     * Update the location of the center based on the built-in masses.
     */

    private void updateCenter () {
        if (myCenterOfMassPoint == null) {
            myCenterOfMassPoint = new Location(0, 0);
        }
        setCenter(myMasses);
    }

    /**
     * updates this assembly by applying forces and moving the nesscary components
     * 
     * @param elapsedTime - time since last update
     * @param bounds - current location of walls in simulation
     * @param globalForces - current forces acting on assembly
     */
    public void update (double elapsedTime, Dimension bounds, List<Force> globalForces) {
        setForces(globalForces);
        update(elapsedTime, bounds);
    }

    /**
     * updates the springs and masses with respect to any forces applied and previous velocity
     * 
     * @param elapsedTime - time since last update
     * @param bounds - current location of the walls in the simulation
     */
    public void update (double elapsedTime, Dimension bounds) {
        for (Spring s : mySprings) {
            s.update(elapsedTime, bounds);
        }
        updateCenter();
        for (Mass m : getMasses()) {
            for (Force f : myGlobalForces) {
                m.applyForce(f.getForceOn(m));
            }
            m.update(elapsedTime, bounds);
        }
    }

    /**
     * set the forces that act on this assembly
     * 
     * @param forces - forces that will be able to act on this assembly
     */
    public void setForces (List<Force> forces) {
        myGlobalForces = forces;
    }

}
